<?php
/**
 * Модель записи пользователя.
 */
class Users_Model_User extends Users_Model_Abstract implements Zend_Acl_Role_Interface, Workset_Acl_Rating_Interface, Workset_Model_Object_Interface {

	const GUEST_LOGIN = 'Guest';
	const SYSTEM_LOGIN = 'System';

	const GET_BY_LOGIN = 1;
	const GET_BY_ID = 2;
	const GET_BY_EMAIL = 3;

	/**
	 * Список объектов пользователей.
	 * 
	 * @var array
	 */
	private static $_aUsers = array();

	/**
	 * Фабрика получения объекта пользователя по его числовому идентификатору.
	 * 
	 * @param integer $iUserId
	 * @return Users_Model_User
	 */
	public static function getById($iUserId) {
		return self::getUser(intval($iUserId),self::GET_BY_ID);
	}

	/**
	 * Фабрика получения объекта пользователя по его нику (логину).
	 * 
	 * @param string $sLogin
	 * @return Users_Model_User
	 */
	public static function getByLogin($sLogin) {
		return self::getUser((string)$sLogin,self::GET_BY_LOGIN);
	}

	/**
	 * Фаьрика получения объекта пользователя по адресу его электронной почты.
	 * 
	 * @param string $sEMail
	 * @return Users_Model_User
	 */
	public static function getByEMail($sEMail) {
		return self::getUser((string)$sEMail,self::GET_BY_EMAIL);
	}

	/**
	 * Фабрика получения объекта пользователя.
	 * 
	 * @param string|integer $siLoginOrId
	 * @param mixed $mGetBy
	 * @return Users_Model_User
	 */
	public static function getUser($siLoginOrId = null, $mGetBy = null) {
		// Если не заданы логин или идентификатор, то отдаём объект текущего пользователя.
		if ($bCurrentUser = ($siLoginOrId === null)) {
			$mGetBy = self::GET_BY_LOGIN;
			$siLoginOrId = (!$oAuth = Zend_Registry::get('auth')) || !$oAuth->hasIdentity()
				?( php_sapi_name() == 'cli'
					? self::SYSTEM_LOGIN
					: self::GUEST_LOGIN
				): $oAuth->getIdentity();
		}

		// Если не задан тип поиска, то определяем его автоматически.
		if ($mGetBy === null) {
			$oValidateDigit = new Zend_Validate_Digits();
			$oValidateEMail = new Zend_Validate_EmailAddress();

			$mGetBy = !$oValidateDigit->isValid($siLoginOrId)
				?( $oValidateEMail->isValid($siLoginOrId)
					? self::GET_BY_EMAIL
					: self::GET_BY_LOGIN
				): self::GET_BY_ID;
		}

		// Небольшая проверочка.
		if ($mGetBy === self::GET_BY_ID && !is_int($siLoginOrId)) {
			$siLoginOrId = intval($siLoginOrId);
		}

		// Если объект не закеширован, то создаём его.
		if (!self::hasUserInCache($siLoginOrId)) {
			// Определение названия класса.
			switch ($siLoginOrId) {
				case self::GUEST_LOGIN:
					$sClassName = 'Users_Model_User_Guest';
					break;
				case self::SYSTEM_LOGIN:
					$sClassName = 'Users_Model_User_System';
					break;
				default:
					$sClassName = 'Users_Model_User_Db';
			}

			// Создание объекта класса и запоминание его в оперативном кэше.
			try {
				self::putUserToCache(new $sClassName($siLoginOrId,$mGetBy));
			}
			// Если изменился логин или удалён, но при этом остался в сессии, то...
			catch (Users_Model_Exception $oException) {
				if ($bCurrentUser && ($oAuth = Zend_Registry::get('auth')) && $oAuth->hasIdentity()) {
					$oAuth->clearIdentity();
                    throw $oException;
					return self::getUser();
				}
				else { // ...иначе не препятствуем бросанию исключения.
					throw $oException;
				}
			}
		}

		// Возвращаем объект пользователя.
		return self::getUserFromCache($siLoginOrId);
	}

	/**
	 * Запоминание объекта пользователя в кэше.
	 * 
	 * @param Users_Model_User $oUser
	 */
	public static function putUserToCache(Users_Model_User $oUser) {
		self::$_aUsers[$oUser->getId()] = $oUser;
		self::$_aUsers[$oUser->getLogin()] = $oUser;
		self::$_aUsers[$oUser->getEMail()] = $oUser;
	}

	/**
	 * Проверка наличия в кэше объекта для конкретного пользователя.
	 * 
	 * @param integer|string|Users_Model_User $mIndentity
	 * @return boolean
	 */
	public static function hasUserInCache($mIndentity) {
		if ($mIndentity instanceof Users_Model_User) {
			$mIndentity = $mIndentity->getId();
		}

		return isset(self::$_aUsers[$mIndentity]);
	}

	/**
	 * Получение объекта пользователя из кэша.
	 * 
	 * @param mixed $mIndentity
	 * @return Users_Model_User
	 */
	public static function getUserFromCache($mIndentity) {
		return self::$_aUsers[$mIndentity];
	}

	/**
	 * Удаление объекта пользователя из кэша.
	 * 
	 * @param $mIdentity
	 * @return unknown_type
	 */
	public static function removeUserFromCache($mIndentity) {
		if ($mIndentity instanceof Users_Model_User) {
			$mIndentity = $mIndentity->getId();
		}

		if (self::hasUserInCache($mIndentity)) {
			$oUser = self::getUserFromCache($mIndentity);
			unset(self::$_aUsers[$oUser->getId()]);
			unset(self::$_aUsers[$oUser->getLogin()]);
			unset(self::$_aUsers[$oUser->getEMail()]);
		}
	}

	/**
	 * Список полей записи пользователя в БД.
	 * 
	 * @var array
	 */
	public $_aFields = array();

	/**
	 * Флаг работы в режиме только чтение.
	 * 
	 * @var boolean
	 */
	protected $_bReadOnly = true;

	/**
	 * Массив изменённых полей.
	 * 
	 * @var array
	 */
	protected $_aChanges = array();

	/**
	 * Создание объекта данного класса.
	 * 
	 * @param mixed $mIdentity
	 * @param mixed $mGetBy
	 */
	protected function __construct($mIdentity, $mGetBy) {
	}

	/**
	 * Получение значения поля.
	 * 
	 * @param string $sName
	 * @param mixed $mDefaultValue
	 * @return mixed
	 */
	protected function _getField($sName,$mDefaultValue = null) {
		return !isset($this->_aChanges[$sName])
			?( isset($this->_aFields[$sName])
				? $this->_aFields[$sName]
				: $mDefaultValue
			): $this->_aChanges[$sName];
	}

	/**
	 * Задание нового значения поля.
	 * 
	 * @param string $sName
	 * @param mixed $mValue
	 */
	protected function _setField($sName,$mValue) {
		if ($this->_bReadOnly) {
			throw new Zend_Exception('Object work in "Read Only" mode.');
		}

		$this->_aChanges[$sName] = $mValue;
	}

	/**
	 * Проверка наличия прав на выполнение привилегии у ресурса.
	 * 
	 * Если пользователь был удалён, то у него нет никаких прав.
	 * 
	 * @param Zend_Acl_Resource_Interface|string $mResource
	 * @param string $sPrivilege
	 * @return boolean
	 */
	public function isAllowed($mResource = null, $sPrivilege = null) {
		return  $this->isAdmin()
                    || 
                (!$this->isDeleted() && Zend_Registry::get('acl')->isAllowed($this,$mResource,$sPrivilege));
	}

	/**
	 * Получение флага того, что пользователь является гостем.
	 * 
	 * @return boolean
	 */
	public function isGuest() {
		return $this->getLogin() == self::GUEST_LOGIN;
	}

	/**
	 * Проверка того, что пользователь является администратором системы.
	 * 
	 * @return boolean
	 */
	public function isAdmin() {
		$oAcl = Zend_Registry::get('acl');
		$sCode = '!'.$oAcl->getOptions(Workset_Acl::OP_ADMIN_GROUP_CODE);
		return $oAcl->inheritsRole($this,$sCode);
	}

	/**
	 * Пользователь имеет право смотреть не опубликованные материалы.
	 *
	 * @return boolean
	 */
	public static function isPublisher() {
        $user = self::getUser();
		return $user->isAdmin() || $user->isAllowed('Material', 'preview');
	}

	/**
	 * Проверка того, что пользователь входит в конкретную группу.
	 * 
	 * @param array|string $asGroupCode
	 * @return boolean
	 */
	public function inGroup($asGroupCode) {
		$bResult = false;
		$oAcl = Zend_Registry::get('acl');
		$asGroupCode = (array)$asGroupCode;

		for($sGroupCode = reset($asGroupCode); $sGroupCode && !$bResult; $sGroupCode = next($asGroupCode) ) {
			$bResult = $oAcl->inheritsRole($this,"!$sGroupCode");
		}

		return $bResult;
	}

	/**
	 * Получение идентификатора роли для ACL объекта.
	 * 
	 * @return string
	 */
	public function getRoleId() {
		return '~'.$this->getId();
	}

	/**
	 * Получение рейтинга пользователя.
	 * 
	 * @return integer
	 */
	public function getRating() {
		return $this->_getField('rating',0);
	}

	/**
	 * Получение секретного слова пользователя.
	 * 
	 * @return string
	 */
	public function getSecretWord() {
		return $this->_getField('secret_word','');
	}

	/**
	 * Изменениек секретного слова пользователя.
	 * 
	 * @return Users_Model_User
	 */
	public function changeSecretWord() {
		$sS = '0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm';

		for ($i = 0, $sSecretWord = ''; $i < 32; $i++) {
			$sSecretWord.= $sS[rand(0,31)];
		}

		$this->_setField('secret_word',$sSecretWord);
		return $this;
	}

	/**
	 * Получение хэша пароля пользователя.
	 * 
	 * @return string
	 */
	public function getPasswordHash() {
		return $this->_getField('password','');
	}

	/**
	 * Задание хэша пароля пользователя.
	 * 
	 * @param string $sHash
	 */
	public function setPasswordHash($sHash) {
		$this->_setField('password',$sHash);
	}

	/**
	 * Получение массива с описанием файла аватара пользователя.
	 *
	 * @return array
	 */
	public function getAvatar() {
		$result = $this->_getField('avatar');
		return empty($result) ? array() : unserialize($result);
	}

	/**
	 * Задание аватара пользователя.
	 *
	 * @param array|null $avatar
	 */
	public function setAvatar(array $avatar = null) {
		$prev = $this->_getField('avatar');

		if (!empty($prev)) {
			$file = unserialize($prev);

			/** @var Workset_File_Info $file */
			$file = Workset_File_Info_Helper::createInfo($file['shortpath'], false);
			unlink($file->getPathname());
		}

		$this->_setField('avatar', serialize($avatar));
	}

	/**
	 * Получение числового идентификатора пользователя.
	 * 
	 * @return integer
	 */
	public function getId() {
		return intval($this->_getField('id',0));
	}

	/**
	 * Получение логина пользователя.
	 * 
	 * @return string
	 */
	public function getLogin() {
		return $this->_getField('login','');
	}

	/**
	 * Задание нового логина пользователя.
	 * 
	 * @param string $sLogin
	 * @return Users_Model_User
	 */
	public function setLogin($sLogin) {
		$this->_setField('login',$sLogin);
		return $this;
	}

	/**
	 * Получение фамилии пользователя.
	 * 
	 * @return string
	 */
	public function getLastName() {
		return $this->_getField('last_name','');
	}

	/**
	 * Задание фамилии пользователя.
	 * 
	 * @param string $sLastName
	 * @return Users_Model_User
	 */
	public function setLastName($sLastName) {
		$this->_setField('last_name',$sLastName);
		return $this;
	}

	/**
	 * Получение имени пользователя.
	 * 
	 * @return string
	 */
	public function getFirstName() {
		return $this->_getField('first_name','');
	}

	/**
	 * Задание имени пользователя.
	 * 
	 * @param string $sFirstName
	 * @return Users_Model_User
	 */
	public function setFirstName($sFirstName) {
		$this->_setField('first_name',$sFirstName);
		return $this;
	}

	/**
	 * Получение отчества пользователя.
	 * 
	 * @return string
	 */
	public function getPatronymic() {
		return $this->_getField('patronymic','');
	}

	/**
	 * Задание отчества пользователя.
	 * 
	 * @param string $sPatronymic
	 * @return Users_Model_User
	 */
	public function setPatronymic($sPatronymic) {
		$this->_setField('patronymic',$sPatronymic);
		return $this;
	}

	/**
	 * Получение адреса электронной почты пользователя.
	 * 
	 * @return string
	 */
	public function getEMail() {
		return $this->_getField('email','');
	}

	/**
	 * Задание адреса эл.почты пользователя.
	 * 
	 * @param string $sEMail
	 * @return Users_Model_User
	 */
	public function setEMail($sEMail) {
		$this->_setField('email',$sEMail);
		return $this;
	}

	/**
	 * Получение времени создания записи.
	 * 
	 * @return integer
	 */
	public function getCreated() {
		return $this->_getField('created',0);
	}

	/**
	 * Получение флага работы в режиме "только чтение".
	 * 
	 * @return boolean
	 */
	public function isReadOnly() {
		return $this->_bReadOnly;
	}

	/**
	 * Получение флага наличия не сохранённых изменений.
	 * 
	 * @return boolean
	 */
	public function isChanged() {
		return count($this->_aChanges)
			? count(array_diff_assoc($this->_aChanges,$this->_aFields)) > 0
			: false;
	}

	/**
	 * Получение флага того, что данный пользователь удалён.
	 * 
	 * @return boolean
	 */
	public function isDeleted() {
		return isset($this->_aFields['status']) && $this->_aFields['status'] < 0;
	}

	/**
	 * Флаг необходимости отправки копий внутренних сообщений на e-mail.
	 * 
	 * @return boolean
	 */
	public function getMessageByEMail() {
		return isset($this->_aFields['message_by_email'])
			&& $this->_aFields['message_by_email'] == 'Y';
	}

	/**
	 * Изменение флага необходимости отправки внутренних сообщений на e-mail.
	 * 
	 * @param boolean $bFlag
	 * @return Users_Model_User
	 */
	public function setMessageByEMail($bFlag) {
		$this->_setField('message_by_email',$bFlag == true ? 'Y' : 'N');
		return $this;
	}

	/**
	 * Представление объекта в виде массива.
	 * 
	 * @return array
	 */
	public function toArray() {
		return array_merge($this->_aFields, $this->_aChanges);
	}

	/**
	 * Сброс внесённых изменений.
	 */
	public function resetChanges() {
		$this->_aChanges = array();
	}

    /**
     * Возвращает поля объекта
     *
     * @return array|Workset_Model_Row_Interface
     */
    public function getData() {
        return $this->_aFields;
    }

    /**
     * Устанавливает новые значения полей объекта
     *
     * @param  array|Workset_Model_Row_Interface $data
     * @return Users_Model_User
     */
    public function setData($data) {
        if (!is_array($data)) {
            if ($data instanceof Workset_Model_Row_Interface) {
                $data = $data->toArray();
            } elseif (!$data instanceof Iterator) {
                throw new Users_Model_Exception('Invalid data format');
            }
        }

        foreach ($data as $key => $val) {
            $this->_setField($key, $val);
        }

        return $this;
    }

	/**
	 * Сохранение изменений.
	 * 
	 * @return boolean
	 */
	public function save() {
		return false;
	}

	/**
	 * Удаление объекта.
	 * 
	 * @return boolean
	 */
	public function delete() {
		return false;
	}

    /**
     * @param  $key
     * @param  $value
     * @return void
     *
     * Implements ArrayAccess Interface
     */
    public function offsetSet($key, $value) {
        if (array_key_exists($key, $this->_appData)) {
            $this->_appData[$key] = $value;
        } else {
            $this->_data[$key] = $value;
        }
    }

    /**
     * @param  $key
     * @return Workset_Model_ObjectInterface
     *
     * Implements ArrayAccess Interface
     */
    public function offsetGet($key) {
        if (array_key_exists($key, $this->_appData)) {
            return $this->_appData[$key];
        } else {
            return $this->_data[$key];
        }
    }

    /**
     * @param  $key
     * @return boolean
     *
     * Implements ArrayAccess Interface
     */
    public function offsetExists($key) {
        return isset($this->_data[$key]) || isset($this->_appData[$key]);
    }

    /**
     * @param  $key
     * @return void
     *
     * Implements ArrayAccess Interface
     */
    public function offsetUnset($key) {
        if (array_key_exists($key, $this->_appData)) {
            unset($this->_appData[$key]);
        } else {
            unset($this->_data[$key]);
        }
    }

    /**
     * Implements IteratorAggregate Interface
     *
     * @return ArrayIterator
     */
    public function getIterator() {
        $object = new ArrayObject($this->toArray());
        return $object->getIterator();
    }
    
}